/***************************************************************************
 * IPPROWSET.CPP
 *
 *  OWebFolder: Implementation for CMSDAIPPRowset (Folder Data)
 *
 *  Copyright (c)1999-2002 Microsoft Corporation, All Rights Reserved
 *  Written by DSOI Office Integration, Microsoft Developer Support
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 *  EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 ***************************************************************************/
#include "owebfolder.h"

///////////////////////////////////////////////////////////////////////////////////
// MSDAIPP_ACCESSORBINDINGS
//
//  These bindings are used to create accessors that match the MSDAIPP_ROWSETINFO
//  and MSDAIPP_COLLECTIONINFO structres (respectively). This allows us to copy data
//  directly from the provider into the structures. Think of them as type information
//  for each struct so the provider knows where to write data in memory.
//
MSDAIPP_ACCESSORBINDINGS s_abindRowsetInfo[] = 
{
	{RESOURCE_ABSOLUTEPARSENAME,  DBTYPE_WSTR,      (MSDAIPP_MAX_URL_LENGTH * sizeof(WCHAR))},
	{RESOURCE_DISPLAYNAME,        DBTYPE_WSTR,      (MAX_PATH * sizeof(WCHAR))              },
	{RESOURCE_CONTENTTYPE,        DBTYPE_WSTR,      (MAX_PATH * sizeof(WCHAR))              },
	{RESOURCE_STREAMSIZE,         DBTYPE_UI8,       sizeof(DWORDLONG)      },
	{RESOURCE_CREATIONTIME,       DBTYPE_FILETIME,  sizeof(FILETIME)       },
	{RESOURCE_LASTWRITETIME,      DBTYPE_FILETIME,  sizeof(FILETIME)       },
	{RESOURCE_ISCOLLECTION,       DBTYPE_BOOL,      sizeof(VARIANT_BOOL)   },
	{RESOURCE_ISREADONLY,         DBTYPE_BOOL,      sizeof(VARIANT_BOOL)   },
	{RESOURCE_ISHIDDEN,           DBTYPE_BOOL,      sizeof(VARIANT_BOOL)   },
	{RESOURCE_ISMARKEDFOROFFLINE, DBTYPE_BOOL,      sizeof(VARIANT_BOOL)   }
};
#define NUM_ROWSETINFO_COLUMNS   (sizeof(s_abindRowsetInfo)/sizeof((s_abindRowsetInfo)[0]))

// Static const for workaround in CMSDAIPPRowset::OpenRowset...
static const LPOLESTR s_poszIPP1ContentType = RESOURCE_CONTENTCLASS;

MSDAIPP_ACCESSORBINDINGS s_abindNewCollectionInfo[] = 
{
	{RESOURCE_PARSENAME,          DBTYPE_WSTR,      (MAX_PATH * sizeof(WCHAR))},
	{RESOURCE_ISCOLLECTION,       DBTYPE_BOOL,      sizeof(VARIANT_BOOL)   },
	{RESOURCE_ISHIDDEN,           DBTYPE_BOOL,      sizeof(VARIANT_BOOL)   }
};
#define NUM_NEWCOLLECTION_COLUMNS (sizeof(s_abindNewCollectionInfo)/sizeof((s_abindNewCollectionInfo)[0]))


///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset
//
//  The CMSDAIPPRowset class does most of the heavy lifting. There can be multiple
//  instances of this class for each (sub) folder open, but generally they will share
//  a session (CMSDAIPPSession) and common base URL.
//
//  The purpose of the class is to wrap an IRowset and do the binding needed to 
//  iterate the folder, add/remove items, and update row information.
//
CMSDAIPPRowset::CMSDAIPPRowset(LPCWSTR pwszURL, CMSDAIPPSession* pSession)
{
    TRACE1("New CMSDAIPPRowset created for URL (%S).\n", pwszURL);
	m_cRef           = 1;
	m_pIPPSession    = pSession;
	m_pRowset        = NULL;
	m_pAccessor      = NULL;
	m_hAccessor      = NULL;
    m_bstrFolderURL  = SysAllocString(pwszURL);
	ADDREF_INTERFACE(m_pIPPSession);
}

CMSDAIPPRowset::~CMSDAIPPRowset(void)
{
    ODS("CMSDAIPPRowset destroyed.\n");
	RELEASE_INTERFACE(m_pIPPSession);
	if (m_bstrFolderURL) SysFreeString(m_bstrFolderURL);
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::CreateMsdaippRowset (static)
//
//  This static creation method takes a copy of the URL the rowset will be for and
//  the session it will bind to. It will return a new (unbound) rowset.
//
STDMETHODIMP_(CMSDAIPPRowset*) CMSDAIPPRowset::CreateMsdaippRowset(LPCWSTR pwszURL, CMSDAIPPSession* pSession)
{
    ODS("Called CMSDAIPPRowset::CreateMsdaippRowset\n");
	ASSERT(pSession); CHECK_NULL_RETURN(pSession, NULL);
	ASSERT(pwszURL);  CHECK_NULL_RETURN(pwszURL, NULL);
	CMSDAIPPRowset* prs = new CMSDAIPPRowset(pwszURL, pSession);
	return prs;
}

STDMETHODIMP_(ULONG) CMSDAIPPRowset::Release(void)
{
	if (--m_cRef > 0) return m_cRef;

    ODS("CMSDAIPPRowset in final release\n");

	if ((m_pAccessor) && (m_hAccessor))
	{
		m_pAccessor->ReleaseAccessor(m_hAccessor, NULL);
		m_hAccessor = NULL;
	}

	RELEASE_INTERFACE(m_pAccessor);
	RELEASE_INTERFACE(m_pRowset);
	delete this;
	return 0;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::OpenRowset 
//
//  The first method you should call on a rowset. This method allows us to both (1)
//  initialize the current rowset and bind to the URL given earlier; and (2) create a
//  new rowset based on the row URL passed in here, which acts to open a new CMSDAIPPRowset
//  object as a child rowset (i.e., sub folder).
//
STDMETHODIMP CMSDAIPPRowset::OpenRowset(LPWSTR pwszRow, CMSDAIPPRowset **ppMsdaippRowset)
{
	HRESULT          hr        = E_FAIL;
	CMSDAIPPRowset*  prset     = NULL;
	IOpenRowset*     popenrow  = NULL;
	DBID             dbid;

	ASSERT(m_pIPPSession); CHECK_NULL_RETURN(m_pIPPSession,  E_ACCESSDENIED);

 // If we are not asking for a subfolder, and we have already been initialized, then
 // we have made a mistake somewhere and should error (I don't expect this to happen)...
	if ((ppMsdaippRowset == NULL) && (m_pRowset))
		return DB_E_ALREADYINITIALIZED;

 // If the caller is asking for a new rowset (most likely a child object), then 
 // make a new CMSDAIPPRowset and call its OpenRowset method to bind there. We then
 // can return that object as a new rowset...
	if (ppMsdaippRowset)
	{
		prset = CMSDAIPPRowset::CreateMsdaippRowset(pwszRow, m_pIPPSession);
		if (prset == NULL) LEAVE2(hr, E_OUTOFMEMORY, "CreateMsdaippRowset failed");

		hr = prset->OpenRowset(pwszRow, NULL);
		LEAVE_ON_ERROR(hr, "CMSDAIPPRowset::OpenRowset failed");

		(*ppMsdaippRowset = prset)->AddRef();
	}
	else
	{
	 // Otherwise, we are being asked to make a rowset for this object (as a new binding).
	 // Set the DBID name for the bind and then ask the session to open an IRowset from
	 // it. Once we have a new rowset, we should set up our accessor...
		memset(&dbid, 0, sizeof(DBID));
		dbid.eKind = DBKIND_NAME;
		dbid.uName.pwszName = pwszRow;

		hr = m_pIPPSession->GetSessionObj()->QueryInterface(IID_IOpenRowset, (void**)&popenrow);
		RETURN_ON_ERROR(hr, "QI for IOpenRowset failed");

		hr = popenrow->OpenRowset(NULL, &dbid, NULL, IID_IRowset, 0, NULL, (IUnknown**)&m_pRowset);
		LEAVE_ON_ERROR(hr, "IOpenRowset::OpenRowset failed");

     // BUG WORKAROUND: Unfortunately, some versions of the 1.0 provider return the content
     // type as the content class, and therefore we may not get the information we expect.
     // To handle this, we'll check to see if we are running with the old provider, and set
     // the column name to RESOURCE_CONTENTCLASS in order to get the right information...
        if ((m_pIPPSession->GetMsdaippVersion() < 2) && (s_poszIPP1ContentType))
            s_abindRowsetInfo[2].pwszColumnName = s_poszIPP1ContentType;

	 // This interal call does most of the work...
		hr = InternalCreateAccessor(s_abindRowsetInfo,
				NUM_ROWSETINFO_COLUMNS,	&m_pAccessor, &m_hAccessor);
	}

leave_routine:
	RELEASE_INTERFACE(prset);
	RELEASE_INTERFACE(popenrow);

	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::GetNextFolderItem 
//
//  Gets the next MSDAIPP_ROWSETINFO in the rowset. This function delegates to an
//  internal call designed to get multiple items at a time. In the future, the code
//  can be changed to take advantage of this for faster iteration.
//
STDMETHODIMP CMSDAIPPRowset::GetNextFolderItem(LPMSDAIPP_ROWSETINFO pitem)
{
	return InternalGetFolderItems(1, pitem, NULL);
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::ResetFolderList 
//
//  Caller is asking us to reset the interation index. Delegate the call to the rowset.
//
STDMETHODIMP CMSDAIPPRowset::ResetFolderList()
{
	HRESULT hr = S_FALSE;
	if (m_pRowset)
		hr = m_pRowset->RestartPosition(DB_NULL_HCHAPTER);
	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::AddNewCollection 
//
//  Using IRowsetChange, we can add a new row as a collection, thereby making a 
//  sub folder on the server. We then need to fill in the MSDAIPP_ROWSETINFO data
//  for the new item to update the web folder view.
//
STDMETHODIMP CMSDAIPPRowset::AddNewCollection(LPCWSTR pwszFolderName, LPMSDAIPP_ROWSETINFO pitem)
{
	HRESULT          hr        = E_FAIL;
	IRowsetChange*   prowchg   = NULL;
	IAccessor*       paccrow   = NULL;
	HACCESSOR        haccrow   = NULL;
	LPBYTE           pdata     = NULL;
	HROW             hrow      = NULL;
	DWORD            dwlen, i;
	DBBINDSTATUS*    pstat;

	ASSERT(m_pRowset);      CHECK_NULL_RETURN(m_pRowset,      E_ACCESSDENIED);
	ASSERT(pwszFolderName); CHECK_NULL_RETURN(pwszFolderName, E_INVALIDARG);
	ASSERT(pitem);          CHECK_NULL_RETURN(pitem,          E_POINTER);
	memset(pitem, 0, sizeof(MSDAIPP_ROWSETINFO));

 // Ask for IRowsetChange...
	hr = m_pRowset->QueryInterface(IID_IRowsetChange, (void**)&prowchg);
	RETURN_ON_ERROR(hr, "QI for IRowsetChange failed");

 // Build an accessor for the MSDAIPP_COLLECTIONINFO struct, which we'll use to
 // set the properties we want for this new folder. (Right now, the user can only
 // select the folder name, but they could specify properties such as whether it
 // hidden or not, or even a different create date.)
	hr = InternalCreateAccessor(
			s_abindNewCollectionInfo, NUM_NEWCOLLECTION_COLUMNS, &paccrow, &haccrow);
	LEAVE_ON_ERROR(hr, "InternalCreateAccessor failed");

 // Now make an in-memory MSDAIPP_COLLECTIONINFO with bind status array...
	pdata = new BYTE[sizeof(MSDAIPP_COLLECTIONINFO) + (NUM_NEWCOLLECTION_COLUMNS * sizeof(ULONG))];
	if (pdata == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Cannot alloc memory for MSDAIPP_COLLECTIONINFO");

 // Set the data in the MSDAIPP_COLLECTIONINFO (by casting the pointer)...
	dwlen = lstrlenW(pwszFolderName);
	memcpy(((MSDAIPP_COLLECTIONINFO*)pdata)->wszName, pwszFolderName, (dwlen * sizeof(WCHAR)));
	((MSDAIPP_COLLECTIONINFO*)pdata)->bIsSubFolder = VARIANT_TRUE;
	((MSDAIPP_COLLECTIONINFO*)pdata)->bIsHidden    = VARIANT_FALSE;

 // Insert the row into the rowset (which makes the request to the server)...
	hr = prowchg->InsertRow(DB_NULL_HCHAPTER, haccrow, pdata, &hrow);
    if (SUCCEEDED(hr))
    {
	    FREE_MEMPOINTER(pdata);

	 // If this succeeded, it returns an HROW which we can use to get the MSDAIPP_ROWSETINFO
	 // data for the new folder. Build this structure and get the data...
	    pdata = new BYTE[sizeof(MSDAIPP_ROWSETINFO) + (NUM_ROWSETINFO_COLUMNS * sizeof(ULONG))];
	    if (pdata == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Out of memory for MSDAIPP_ROWSETINFO");

	    hr = m_pRowset->GetData(hrow, m_hAccessor, (void*)pdata);
	    LEAVE_ON_ERROR(hr, "IRowset::GetData failed");

	 // Check the DBSTATUS of each bound column to make sure we got valid data...
	    pstat = (DBBINDSTATUS*)(pdata + sizeof(MSDAIPP_ROWSETINFO));
	    for (i = 0; i < NUM_ROWSETINFO_COLUMNS; i++)
	    {
		    if ((pstat[i] != DBSTATUS_S_OK) && 
			    (pstat[i] != DBSTATUS_S_ISNULL) && 
			    (pstat[i] != DBSTATUS_S_TRUNCATED))
		    {
			    TRACE1("Unexpected BindStatus (%d)\r\n", pstat[i]);
			    LEAVE2(hr, E_UNEXPECTED, "BindStatus error for MSDAIPP_ROWSETINFO");
		    }
	    }

	 // Copy the data into caller supplied buffer...
	    memcpy(pitem, pdata, sizeof(MSDAIPP_ROWSETINFO));

	 // BUG WORKAROUND: WEC 5 clients don't always give a full URL, only the
	 // name of the folder, so make sure it is a URL (TODO: Make more robust
	 // to actually check the URL and canonicalize via WinInet)...
		if (!LooksLikeHTTP(pitem->wszURL) && (pitem->wszURL[0] != '\0'))
		{
			LPWSTR pwszT = ConstructUrlForFile(m_bstrFolderURL, pitem->wszURL);
			if (pwszT)
			{
				lstrcpyW(pitem->wszURL, pwszT);
				MemFree(pwszT);
			}
		}

    }
    else if (hr == DB_E_ERRORSOCCURRED)
    {
        MSDAIPP_ROWSETINFO* pitemT = NULL;

	 // BUG WORKAROUND: If InsertRow failed with DB_E_ERRORSOCCURRED, it may not be a
	 // real failure per se. There is a bug in the 1.0 provider that can cause
	 // this error to be returned even though the insert was successful. We can't
	 // use the HROW when the bug occurs, but we can get the folder info by binding
	 // to the new URL. If this works, we can ignore the error.

	 // Make the URL string where the new folder should be...
        LPWSTR pwszFullUrl = ConstructUrlForFile(m_bstrFolderURL, pwszFolderName);
        if (pwszFullUrl)
        {
		 // Attempt to open the item by the new URL...
            hr = InternalGetFolderItemFromURL(pwszFullUrl, &pitemT);
            if (SUCCEEDED(hr) && (pitemT))
            {
			 // If it works, copy this to the caller supplied buffer...
	            memcpy(pitem, pitemT, sizeof(MSDAIPP_ROWSETINFO));
                MemFree(pitemT);
            }
            MemFree(pwszFullUrl);
        }
    }
    else { LEAVE_ON_ERROR(hr, "IRowsetChange::InsertRow failed"); }


leave_routine:
 // Cleanup any rows or accessors we may have open...
	if (hrow)
		m_pRowset->ReleaseRows(1, &hrow, NULL, NULL, NULL);

	FREE_MEMPOINTER(pdata);

	if ((haccrow) && (paccrow))
		paccrow->ReleaseAccessor(haccrow, NULL);

	RELEASE_INTERFACE(paccrow);
	RELEASE_INTERFACE(prowchg);

	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::DownloadFile 
//
//  Using IBindResource, we will bind to the file specified by LPMSDAIPP_ROWSETINFO
//  and copy it to the location pwszFolder. 
//
STDMETHODIMP CMSDAIPPRowset::DownloadFile(LPMSDAIPP_ROWSETINFO pitem, LPCWSTR pwszFolder)
{
	HRESULT          hr        = E_FAIL;
	IBindResource*   pbdres    = NULL;
	IStream*         pstm      = NULL;
	BYTE*            pbuffer   = NULL;
	DWORD            dwBindFlags, dwStatus;
	DWORD            cbRead, cbWritten;
	LARGE_INTEGER    liStart;
	HANDLE           hFile;

	ASSERT(m_pIPPSession); CHECK_NULL_RETURN(m_pIPPSession, E_ACCESSDENIED);
	ASSERT(pwszFolder);    CHECK_NULL_RETURN(pwszFolder,    E_INVALIDARG);
	ASSERT(pitem);         CHECK_NULL_RETURN(pitem,         E_POINTER);

 // Get IBindResource from the session object...
	hr = m_pIPPSession->GetSessionObj()->QueryInterface(IID_IBindResource, (void**)&pbdres);
	LEAVE_ON_ERROR(hr, "QI for IBindResource failed");

 // Make a 10K buffer for data caching...
	pbuffer = new BYTE[10240];
	if (pbuffer == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Can't alloc 10K buffer");

 // We only need read access for download, but should deny write to avoid changes
 // while we are downloading...
	dwBindFlags = (DBBINDURLFLAG_READ | DBBINDURLFLAG_SHARE_DENY_WRITE);

 // Bind to the URL for the item to download (ask for its IStream)...
	hr = pbdres->Bind(NULL, pitem->wszURL, dwBindFlags, 
			DBGUID_STREAM, IID_IStream, NULL, NULL, &dwStatus, (IUnknown**)&pstm);
	LEAVE_ON_ERROR(hr, "IBindResource::Bind failed");

	liStart.QuadPart = 0;
	pstm->Seek(liStart, STREAM_SEEK_SET, NULL);

 // Open the local file for write access (we will overwrite if file exists!).
	hr = DsoOpenLocalFile(pwszFolder, GENERIC_WRITE, 0, CREATE_ALWAYS, &hFile);
	LEAVE_ON_ERROR(hr, "Can't open local file for write access");

	while (TRUE)
	{
	 // Read 10K worth of data from the web (break if we reached EOF)...
		if (FAILED(hr = pstm->Read((void*)pbuffer, 10240, &cbRead)) ||
			(cbRead == 0))
			break;

	 // Write that data to the file...
		if (FALSE == WriteFile(hFile, pbuffer, cbRead, &cbWritten, NULL))
		{
			hr = HRESULT_FROM_WIN32(GetLastError());
			break;
		}
	}

 // Set the end of the file and the Create/Modified times...
	SetEndOfFile(hFile);
	SetFileTime(hFile, &(pitem->ftCreateTime), NULL, &(pitem->ftLastModified));
	CloseHandle(hFile);

leave_routine:
	FREE_MEMPOINTER(pbuffer);
	RELEASE_INTERFACE(pstm);
	RELEASE_INTERFACE(pbdres);
    return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::UploadFile 
//
//  Pushes the file to the server using ICreateRow. Returns the new file information
//  in the LPMSDAIPP_ROWSETINFO* specified.
//
STDMETHODIMP CMSDAIPPRowset::UploadFile(LPCWSTR pwszFileToUpload, LPMSDAIPP_ROWSETINFO* ppitem)
{
    HRESULT        hr;
    ICreateRow*    pcrow     = NULL;
	IStream*       pstm      = NULL;
	BYTE*          pbuffer   = NULL;
    LPWSTR         pwszURL   = NULL;
    LPWSTR         pwszNewUrl= NULL;
	DWORD          dwBindFlags, dwStatus;
	DWORD          cbRead, cbWritten;
	HANDLE         hFile;
	LARGE_INTEGER  liStart;

    TRACE1("CMSDAIPPRowset::UploadFile( %S )\n", pwszFileToUpload);

	ASSERT(m_pIPPSession);     CHECK_NULL_RETURN(m_pIPPSession,    E_ACCESSDENIED);
	ASSERT(pwszFileToUpload);  CHECK_NULL_RETURN(pwszFileToUpload, E_INVALIDARG);
	ASSERT(ppitem);            CHECK_NULL_RETURN(ppitem,           E_POINTER);
    *ppitem = NULL;

 // First, open the local file for read access (the file must exist)...
    hr = DsoOpenLocalFile(pwszFileToUpload, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, &hFile);
    RETURN_ON_ERROR(hr, "Cannot open file");

 // Make a 10K buffer for transfer...
    pbuffer = new BYTE[10240];
	if (pbuffer == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Out of memory for 10K buffer");

 // Ask the session object for ICreateRow...
    hr = m_pIPPSession->GetSessionObj()->QueryInterface(IID_ICreateRow, (void**)&pcrow);
	LEAVE_ON_ERROR(hr, "QueryInterface for ICreateRow failed");

 // Construct the new URL to create a row on, and set the bind flags...
    pwszURL = ConstructUrlForFile(m_bstrFolderURL, pwszFileToUpload);
	if (pwszURL == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Can't make URL string (OOM?)");

    dwBindFlags = ( DBBINDURLFLAG_READWRITE | 
                    DBBINDURLFLAG_SHARE_DENY_WRITE | DBBINDURLFLAG_OVERWRITE);

 // Create the new row on the server and ask for IStream to push content to it...
    hr = pcrow->CreateRow(NULL, pwszURL, dwBindFlags, 
        DBGUID_STREAM, IID_IStream, NULL, NULL, &dwStatus, &pwszNewUrl, (IUnknown**)&pstm);
	LEAVE_ON_ERROR(hr, "ICreateRow::CreateRow failed");

    liStart.QuadPart = 0;
	pstm->Seek(liStart, STREAM_SEEK_SET, NULL);

	while (TRUE)
	{
	 // Read in the data from the file...
		if (FALSE == ReadFile(hFile, pbuffer, 10240, &cbRead, NULL))
		{
			hr = HRESULT_FROM_WIN32(GetLastError());
			break;
		}

	 // Check to see if we got all the data; break the loop if so...
		if (0 == cbRead) break;

	 // Write the data read into the buffer to the server...
		if (FAILED(hr = pstm->Write((void*)pbuffer, cbRead, &cbWritten)))
			break;
	}
	LEAVE_ON_ERROR(hr, "Read/Write failed");

 // If we were successful in writing all the data, commit the changes. This
 // does the actual push up the the server in most cases...
	hr = pstm->Commit(STGC_DEFAULT);
	LEAVE_ON_ERROR(hr, "IStream::Commit failed");

 // If the changes we saved on the server, acquire the row info for the new
 // file to return the caller (to store in the listview)...
    hr = InternalGetFolderItemFromURL(
            (((pwszNewUrl) && (*pwszNewUrl)) ? pwszNewUrl : pwszURL), ppitem);

leave_routine:

    CloseHandle(hFile);

    if (pwszNewUrl)
        CoTaskMemFree(pwszNewUrl);

	FREE_MEMPOINTER(pwszURL);
	FREE_MEMPOINTER(pbuffer);

	RELEASE_INTERFACE(pstm);
	RELEASE_INTERFACE(pcrow);
   
    return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::DeleteURLResource 
//
//  Delete the resource from the server (this will remove both files and folders).
//  We use IScopedOperations for the Delete.
//
STDMETHODIMP CMSDAIPPRowset::DeleteURLResource(LPCWSTR pwszUrlResource)
{
	HRESULT             hr     = E_FAIL;
	IBindResource*      pbdres = NULL;
	IScopedOperations*  piso   = NULL;
	DWORD               dwBindFlags, dwStatus;

	ASSERT(m_pIPPSession);   CHECK_NULL_RETURN(m_pIPPSession,   E_ACCESSDENIED);
	ASSERT(pwszUrlResource); CHECK_NULL_RETURN(pwszUrlResource, E_INVALIDARG);

 // Ask the session for IBindResource...
	hr = m_pIPPSession->GetSessionObj()->QueryInterface(IID_IBindResource, (void**)&pbdres);
	LEAVE_ON_ERROR(hr, "QI for IBindResource failed");

 // We need exclusive access if we are going to delete...
	dwBindFlags = (DBBINDURLFLAG_WRITE | DBBINDURLFLAG_SHARE_EXCLUSIVE);

 // Bind to the URL and ask for IScopedOperations...
	hr = pbdres->Bind(NULL, pwszUrlResource, dwBindFlags, 
			DBGUID_ROW, IID_IScopedOperations, NULL, NULL, &dwStatus, (IUnknown**)&piso);
	LEAVE_ON_ERROR(hr, "IBindResource::Bind failed");

 // If we have exclusive access, delete the item...
	hr = piso->Delete(1, &pwszUrlResource, 0, &dwStatus);
	LEAVE_ON_ERROR(hr, "IScopedOperations::Delete failed");

 // Check the status to make sure it was removed...
	if (dwStatus != DBSTATUS_S_OK)
		hr = E_UNEXPECTED;

leave_routine:
	RELEASE_INTERFACE(piso);
    return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::ReloadRowset 
//
//  Release the current rowset and accessors, and then reload. Forces a full 
//  refreash of the current rowset.
//
STDMETHODIMP CMSDAIPPRowset::ReloadRowset()
{
    ODS("CMSDAIPPRowset::ReloadRowset()\r\n");

	if ((m_pAccessor) && (m_hAccessor))
	{
		m_pAccessor->ReleaseAccessor(m_hAccessor, NULL);
		m_hAccessor = NULL;
	}

	RELEASE_INTERFACE(m_pAccessor);
	RELEASE_INTERFACE(m_pRowset);

    return OpenRowset(m_bstrFolderURL, NULL);
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::InternalCreateAccessor 
//
//  Internal function to build an accessor from the MSDAIPP_ACCESSORBINDINGS info
//  passed by the caller. This accessor can then be used to get row data.
//
STDMETHODIMP CMSDAIPPRowset::InternalCreateAccessor(
	MSDAIPP_ACCESSORBINDINGS *paccinfo, ULONG cCols, IAccessor **ppAccessor, HACCESSOR *phAccessor)
{
    ODS("CMSDAIPPRowset::InternalCreateAccessor\n");
	ASSERT(m_pRowset);

	HRESULT          hr;
	IColumnsInfo*    pcolinfo  = NULL;
    IAccessor*       paccessor = NULL;
	DBID*            pdbidCols = NULL;
	DBBINDSTATUS*    pstatus   = NULL;
	DBBINDING*       pBindings = NULL;
	ULONG*           pulColIds = NULL;
	UINT             i, cbPosition, cbTotalBindSize;

	ASSERT(m_pRowset);    CHECK_NULL_RETURN(m_pRowset,  E_ACCESSDENIED);
	ASSERT(paccinfo);     CHECK_NULL_RETURN(paccinfo,   E_INVALIDARG);
	ASSERT(ppAccessor);   CHECK_NULL_RETURN(ppAccessor, E_POINTER);
	ASSERT(phAccessor);   CHECK_NULL_RETURN(phAccessor, E_POINTER);

	*ppAccessor = NULL;	*phAccessor = NULL;

 // First we get the right ordinals for the columns we want to bind to. Use
 // IColumnsInfo::MapColumnIDs to get the ordinals from the names...
	hr = m_pRowset->QueryInterface(IID_IColumnsInfo, (void**)&pcolinfo);
	LEAVE_ON_ERROR(hr, "QI for IColumnsInfo failed");

	pulColIds = new ULONG[cCols];
	if (pulColIds == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Out of memory for ULONG[]");

	pdbidCols = new DBID[cCols];
	if (pdbidCols == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Out of memory for DBID[]");

	cbTotalBindSize = 0;

 // Set all the names (and calculate the total bind size)...
	for (i = 0; i < cCols; i++)
	{
		pdbidCols[i].eKind = DBKIND_NAME;
		pdbidCols[i].uName.pwszName = paccinfo[i].pwszColumnName;
		cbTotalBindSize += paccinfo[i].ulMaxSize;
	}

 // Map the names to the right IDs...
	hr = pcolinfo->MapColumnIDs(cCols, pdbidCols, pulColIds);
	LEAVE_ON_ERROR(hr, "MapColumnIDs failed");

 // Then set up the binding structure used by the Accessor. This is an in-memory
 // veiw of the struct defined by MSDAIPP_ACCESSORBINDINGS, along with the bind 
 // status for each column...
	hr = m_pRowset->QueryInterface(IID_IAccessor, (void**)&paccessor);
	LEAVE_ON_ERROR(hr, "QI for IAccessor failed");

	pBindings = new DBBINDING[cCols];
	if (pBindings == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Out of memory for DBBINDING[]");

	pstatus = new DBBINDSTATUS[cCols];
	if (pstatus == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Out of memory for DBBINDSTATUS[]");

	cbPosition = 0;

	for (i = 0; i < cCols; i++)
	{
		pBindings[i].iOrdinal   = pulColIds[i];
		pBindings[i].obValue    = cbPosition;
		pBindings[i].obLength   = 0;
		pBindings[i].obStatus   = cbTotalBindSize + (i * sizeof(ULONG));
		pBindings[i].pTypeInfo  = NULL;
		pBindings[i].pObject    = NULL;
		pBindings[i].pBindExt   = NULL;
		pBindings[i].dwPart     = DBPART_VALUE | DBPART_STATUS;
		pBindings[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
		pBindings[i].eParamIO   = 0;
		pBindings[i].cbMaxLen   = paccinfo[i].ulMaxSize;
		pBindings[i].dwFlags    = 0;
		pBindings[i].wType      = paccinfo[i].wColType;
		pBindings[i].bPrecision = 0;
		pBindings[i].bScale     = 0;

		cbPosition += pBindings[i].cbMaxLen;
	}

	hr = paccessor->CreateAccessor(
			DBACCESSOR_ROWDATA,	cCols, pBindings, 0, phAccessor, pstatus);
	LEAVE_ON_ERROR(hr, "CreateAccessor failed");
    

 // Check the bind status to make sure we get the binding layout we want...
	for (i = 0; i < cCols; i++)
	{
		if (pstatus[i] != DBSTATUS_S_OK)
		{
			TRACE1("Bad status on CreateAccessor (%d)\n", pstatus[i]);
			paccessor->ReleaseAccessor(*phAccessor, NULL);
			*phAccessor = NULL;
			LEAVE2(hr, E_UNEXPECTED, "Can't continue if we don't get right binding");
		}
	}

 // Hold on the accessor for this rowset...
	(*ppAccessor = paccessor)->AddRef();

leave_routine:
	RELEASE_INTERFACE(pcolinfo);
	RELEASE_INTERFACE(paccessor);

	FREE_MEMPOINTER(pulColIds);
	FREE_MEMPOINTER(pdbidCols);
	FREE_MEMPOINTER(pBindings);
	FREE_MEMPOINTER(pstatus);
	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::InternalGetFolderItems 
//
//  Internal function to get one or more LPMSDAIPP_ROWSETINFO items from the rowset.
//
STDMETHODIMP CMSDAIPPRowset::InternalGetFolderItems(ULONG citems, LPMSDAIPP_ROWSETINFO pitems, ULONG* pcfetched)
{
	HRESULT          hr = E_OUTOFMEMORY;
	DBBINDSTATUS*    pstat;
	BYTE*            lpdata  = NULL;
	HROW*            phrow   = NULL;
	DWORD            i, j, cRowsObtained;

	ASSERT(m_pRowset);    CHECK_NULL_RETURN(m_pRowset,    E_ACCESSDENIED);
	ASSERT(m_hAccessor);  CHECK_NULL_RETURN(m_hAccessor,  E_ACCESSDENIED);
	ASSERT(pitems);       CHECK_NULL_RETURN(pitems,       E_POINTER);

 // Init the out parameters to empty...
	if (pcfetched) *pcfetched = 0;
	CHECK_NULL_RETURN(citems, S_FALSE);
	memset(pitems, 0, (citems * sizeof(MSDAIPP_ROWSETINFO)));

 // Get the number of rows asked for by the caller...
	hr = m_pRowset->GetNextRows(NULL, 0, citems, &cRowsObtained, &phrow);
	LEAVE_ON_ERROR(hr, "IRowset::GetNextRows failed");

	if ((hr == DB_S_ENDOFROWSET) || (cRowsObtained == 0))
		LEAVE2(hr, S_FALSE, "Reached the end of the rowset");

 // If we are not at the end of the rowset, make a new in-memory copy of
 // MSDAIPP_ROWSETINFO plus the bind status...
	lpdata = new BYTE[sizeof(MSDAIPP_ROWSETINFO) + (NUM_ROWSETINFO_COLUMNS * sizeof(ULONG))];
	if (lpdata == NULL) LEAVE(hr, "Out of memory for GetNextFolderItem");

 // For each row obtained, get the data and copy to caller buffer...
	for (j = 0; j < cRowsObtained; j++)
	{
	 // Get the data from the row...
		hr = m_pRowset->GetData(phrow[j], m_hAccessor, (void*)lpdata);
		LEAVE_ON_ERROR(hr, "IRowset::GetData failed!");

	 // Check the status to be sure of success...
		pstat = (DBBINDSTATUS*)(lpdata + sizeof(MSDAIPP_ROWSETINFO));
		for (i = 0; i < NUM_ROWSETINFO_COLUMNS; i++)
		{
			if ((pstat[i] != DBSTATUS_S_OK) && 
				(pstat[i] != DBSTATUS_S_ISNULL) && 
				(pstat[i] != DBSTATUS_S_TRUNCATED))
			{
				TRACE2("Unexpected BindStatus (%d) for item %d\r\n", pstat[i], i);
				LEAVE2(hr, E_UNEXPECTED, "BindStatus error for MSDAIPP_ROWSETINFO");
			}
		}

	 // Copy this to the caller (minus the bind status)...
		memcpy(&pitems[j], lpdata, sizeof(MSDAIPP_ROWSETINFO));
	}

 // If requested, return the number of items returned...
	if (pcfetched)
		*pcfetched = cRowsObtained;

leave_routine:
 // Free the rows we fetched earlier...
	if (phrow)
		m_pRowset->ReleaseRows(cRowsObtained, phrow, NULL, NULL, NULL);

	FREE_MEMPOINTER(lpdata);
	return hr;
}

///////////////////////////////////////////////////////////////////////////////////
// CMSDAIPPRowset::InternalGetFolderItemFromURL 
//
//  This internal function can get the LPMSDAIPP_ROWSETINFO data for a specific URL,
//  using IRow::GetColumns. Yet another way to bind to the data.
//
STDMETHODIMP CMSDAIPPRowset::InternalGetFolderItemFromURL(LPCWSTR pwszUrl, MSDAIPP_ROWSETINFO **ppitem)
{
	HRESULT         hr      = E_FAIL;
    IBindResource*  pbndres   = NULL;
    IRow*           prow      = NULL;
	DBID*           pdbidCols = NULL;
	DWORD           dwStatus, i;
    LPBYTE          lpOffset;
    DBCOLUMNACCESS  rgColAccess[NUM_ROWSETINFO_COLUMNS];
    MSDAIPP_ROWSETINFO* pout = NULL; 

    TRACE1("CMSDAIPPRowset::InternalGetFolderItemFromURL( %S )\n", pwszUrl);

	ASSERT(m_pIPPSession);   CHECK_NULL_RETURN(m_pIPPSession,  E_ACCESSDENIED);
	ASSERT(pwszUrl);         CHECK_NULL_RETURN(pwszUrl,        E_INVALIDARG);
	ASSERT(ppitem);          CHECK_NULL_RETURN(ppitem,         E_POINTER);
    *ppitem = NULL;

 // Make a new MSDAIPP_ROWSETINFO to return to the caller...
    pout = new MSDAIPP_ROWSETINFO;
	if (pout == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Can't alloc MSDAIPP_ROWSETINFO");

 // Ask the session for IBindResource...
    hr = m_pIPPSession->GetSessionObj()->QueryInterface(IID_IBindResource, (void**)&pbndres);
	LEAVE_ON_ERROR(hr, "QueryInterface for IBindResource failed");

 // Bind the URL given and ask for IRow...
    hr = pbndres->Bind(NULL, pwszUrl, DBBINDURLFLAG_READ, 
        DBGUID_ROW, IID_IRow, NULL, NULL, &dwStatus, (IUnknown**)&prow);
	LEAVE_ON_ERROR(hr, "IBindResource::Bind failed");

 // Set a number of DBIDs for the columns we want to get, add the names to each...
	pdbidCols = new DBID[NUM_ROWSETINFO_COLUMNS];
	if (pdbidCols == NULL) LEAVE2(hr, E_OUTOFMEMORY, "Out of memory for DBID[]");

	for (i = 0; i < NUM_ROWSETINFO_COLUMNS; i++)
	{
		pdbidCols[i].eKind = DBKIND_NAME;
		pdbidCols[i].uName.pwszName = s_abindRowsetInfo[i].pwszColumnName;
	}

    lpOffset = (BYTE*)pout;
    memset(pout, 0, sizeof(MSDAIPP_ROWSETINFO));
    memset(&rgColAccess[0], 0, (NUM_ROWSETINFO_COLUMNS * sizeof(DBCOLUMNACCESS)));

 // For each column, fill in the bind info from the MSDAIPP_ACCESSORBINDINGS settings
 // so the provider can create a dynamic accessor for us...
    for (i = 0; i < NUM_ROWSETINFO_COLUMNS; i++)
    {
        rgColAccess[i].pData = lpOffset;
        rgColAccess[i].columnid = pdbidCols[i];
        rgColAccess[i].cbMaxLen = s_abindRowsetInfo[i].ulMaxSize;
        rgColAccess[i].wType = s_abindRowsetInfo[i].wColType;

        lpOffset += rgColAccess[i].cbMaxLen;
    }

 // Get the data. This fills the data into the structure as we outlined, then destroys
 // the dynamic accessor used in the process...
    hr = prow->GetColumns(NUM_ROWSETINFO_COLUMNS, rgColAccess);
	LEAVE_ON_ERROR(hr, "IRow::GetColumns failed");

 // If that was successful, we can return the data to the caller...
    *ppitem = pout;

leave_routine:

 // If not, we should free the struct...
    if (FAILED(hr)) { FREE_MEMPOINTER(pout); }

	FREE_MEMPOINTER(pdbidCols);
	RELEASE_INTERFACE(prow);
	RELEASE_INTERFACE(pbndres);

    return hr;
}

